/*
 * mixedflowslicerenderer2d.cpp
 *
 *  Created on: 30/07/2012
 *      Author: cvan680
 */


#include "mixedflowslicerenderer2d.h"
#include "modules/flowreen/utils/flowmath.h"
#include "modules/flowreen/datastructures/flow3d.h"

namespace voreen {

MixedFlowSliceRenderer2D::MixedFlowSliceRenderer2D()
    : RenderProcessor(),
    pixelSamplingProp_("pixelSamplingProp", "pixel sampling: ", 50, 1, 10000),
    useAdaptiveKernelSizeProp_("useAdaptiveKernelSizeProp", "use adaptive kernel size: ", false),
    kernelSizeProp_("kernelSizeProp", "max. convolution kernel size: ", 7, 1, 100),
    inport_volume1_(Port::INPORT, "volumehandle.volumehandle"),
    inport_volume2_(Port::INPORT, "volumehandle.volumehandle"),
    outport_img(Port::OUTPORT, "image.outport"),
    permutation_(0, 1, 2)
{

//TODO what is a private renderport? See RenderProcessor::addPrivateRenderPort(RenderPort* port)

    addProperty(kernelSizeProp_);
    addProperty(useAdaptiveKernelSizeProp_);
    addProperty(pixelSamplingProp_);

    addPort(inport_volume1_);
    addPort(inport_volume2_);
    addPort(outport_img);

}

MixedFlowSliceRenderer2D::~MixedFlowSliceRenderer2D() {
	//TODO delete stuff
}

void MixedFlowSliceRenderer2D::deinitialize() throw (tgt::Exception) {
	//TODO removed some stuff from flowslicerenderer
    RenderProcessor::deinitialize();
}

void MixedFlowSliceRenderer2D::initialize() throw (tgt::Exception) {
    FlowreenProcessor::init();
    RenderProcessor::initialize();
}

// taken from FlowSliceRenderer.cpp
tgt::Texture* MixedFlowSliceRenderer2D::renderFlowTexture(const Flow3D& flow1,
												   const Flow3D& flow2,
												   const size_t sliceNo,
                                                   const int textureScaling,
                                                   const tgt::vec2& thresholds)
{
    const size_t sampling = static_cast<size_t>(pixelSamplingProp_.get());

    // TODO figure out where we get textureScaling and thresholds parameters from

    // TODO make sure dimensions of flow1 and flow2 are equal
    // --> this is also checked in fastLIC algorithm, so it'll work out either way

    // get the size of the slice and calculate the output texture's size, based
    // on the nearest integral scaling factor.
    //
    tgt::ivec2 sliceSize = flow1.getFlowSliceDimensions(permutation_);

    float* pixels = 0;

    Flow2D flow2D_1 = flow1.extractSlice(permutation_, sliceNo);
    Flow2D flow2D_2 = flow2.extractSlice(permutation_, sliceNo);
	const int kernelSize = kernelSizeProp_.get();
	SimpleTexture<float> noiseTexture(flow2D_1.dimensions_, true); // create 1 white noise texture.. maybe create 2 different LIC textures?
	noiseTexture.createWhiteNoise();
	pixels = StreamlineTexture<float>::fastLIC(flow2D_1, noiseTexture,
		textureScaling, sampling, kernelSize, thresholds, useAdaptiveKernelSizeProp_.get());


	// either use fastLIC for both and then merge results, or calculate results inside the fastLIC algorithm

	if (pixels == 0)
		return 0;

	// no memory leak occurs when not deleting pointer pixels here, because
	// tgt::Texture's dtor will free the memory by using delete []
	//
	return new tgt::Texture(reinterpret_cast<GLubyte*>(pixels),
		tgt::ivec3(sliceSize * textureScaling, 1), GL_LUMINANCE, GL_LUMINANCE,
		GL_FLOAT, tgt::Texture::NEAREST);
}


// taken from streamlinetexture.cpp
template<typename T>
T* MixedFlowSliceRenderer2D<T>::fastLIC(const Flow2D& flow1, const Flow2D& flow2,
										const SimpleTexture<float>& inputTexture,
                                 	    const size_t textureScaling, const size_t sampling,
                                 	    const int maxKernelSize, const tgt::vec2& thresholds,
                                 	    const bool useAdaptiveKernelSize)
{

    const tgt::ivec2 inputTexSize(inputTexture.getDimensions().xy());
    if (flow1.dimensions_ != inputTexSize || flow2.dimensions_ != inputTexSize)
        return 0;

    StreamlineTexture<T> outputTexture(inputTexSize * static_cast<int>(textureScaling));
    const tgt::ivec2& outputTexSize = outputTexture.getDimensions().xy();
    const int delta = static_cast<int>(sampling);
    const float stepSize = (0.5f / textureScaling);
    const float length = static_cast<float>(tgt::max(flow1.dimensions_)) * 1.10f;

    size_t numStreamlines = 0;
    for (int y = 0; y < outputTexSize.y; y += delta) {
        for (int x = 0; x < outputTexSize.x; x += delta) {
            tgt::ivec2 ir0(x, y);
            if (outputTexture[ir0].counter_ > 0)
                continue;

            // get the coordinates of the pixel in the input texture which corresponds
            // to this position in the output texture and calculate its position within
            // the flow.
            //
            tgt::ivec2 r0Input(ir0 / static_cast<int>(textureScaling));
            tgt::ivec2 errorInput(0, 0);
            tgt::vec2 r0 = flow.slicePosToFlowPos(r0Input, inputTexSize, &errorInput);
            const tgt::vec2 v = flow.lookupFlow(r0);

            if (v == tgt::vec2::zero)
                continue;

            // start streamline computation
            //
            int indexR0 = 0;
            std::deque<tgt::vec2> streamlineD =
                FlowMath::computeStreamlineRungeKutta(flow, r0, length, stepSize, &indexR0, thresholds);
            if (streamlineD.size() <= 1)
                continue;

            // also determine the round-off error which occurs if the flow positions was
            // converted back directly to the coordinates of the output textures.
            //
            tgt::ivec2 errorOutput(0, 0);
            flow.slicePosToFlowPos(ir0, outputTexSize, &errorOutput);

            ++numStreamlines;
            std::vector<tgt::vec2> streamline = FlowMath::dequeToVector(streamlineD);

            // copy the streamline for second coordinate conversion
            //
            std::vector<tgt::vec2> streamlineCopy(streamline);

            // convert the streamline into dimensions of the input texture
            //
            std::vector<tgt::ivec2> streamlineInput =
                flow.flowPosToSlicePos(streamline, inputTexSize, errorInput);

            // also convert the streamline into dimensions of the output texture
            //
            std::vector<tgt::ivec2> streamlineOutput =
                flow.flowPosToSlicePos(streamlineCopy, outputTexSize, errorOutput);

            // calculate initial intensity for the starting pixel
            //
            int L = maxKernelSize;
            if (useAdaptiveKernelSize == true)
                L = static_cast<int>(tgt::round(maxKernelSize * (tgt::length(v) / thresholds.y)));
            const float k = 1.0f / (((2 * L) + 1));
            float intensity0 = k * fastLICIntensity(inputTexture, indexR0, L, streamlineInput);

            // determine the affected pixel in the output texture and add the
            // initial intensity
            //
            tgt::ivec2& outputTexCoord = streamlineOutput[indexR0];
            outputTexture[outputTexCoord].elem_ += T(intensity0);
            ++(outputTexture[outputTexCoord].counter_);

            // trace streamline in forward direction and update intensity
            //
            float intensity = intensity0;
            int left = indexR0 + L + 1;
            int right = indexR0 - L;
            const int numPoints = static_cast<int>(streamlineInput.size());

            for (int i = (indexR0 + 1); i < numPoints; ++i, ++left, ++right) {
                int l = (left >= numPoints) ? (numPoints - 1) : left;
                int r = (right <= 0) ? 0 : right;

                intensity += (inputTexture[streamlineInput[l]] - inputTexture[streamlineInput[r]]) * k;

                outputTexCoord = streamlineOutput[i];
                outputTexture[outputTexCoord].elem_ += T(intensity);
                ++(outputTexture[outputTexCoord].counter_);
            }   // for (i

            // trace streamline in backward direction and update intensity
            //
            intensity = intensity0;
            left = indexR0 - L - 1;
            right = indexR0 + L;
            for (int i = (indexR0 - 1); i >= 0; --i, --left, --right) {
                int l = (left <= 0) ? 0 : left;
                int r = (right >= numPoints) ? (numPoints - 1) : right;

                intensity += (inputTexture[streamlineInput[l]] - inputTexture[streamlineInput[r]]) * k;

                outputTexCoord = streamlineOutput[i];
                outputTexture[outputTexCoord].elem_ += T(intensity);
                ++(outputTexture[outputTexCoord].counter_);
            }   // for (i
        }   // for (x
    }   // for (y

    size_t unhitPixels = 0;
    const size_t numPixels = outputTexture.getNumElements();
    for (size_t i = 0; i < numPixels; ++i) {
        if (outputTexture[i].counter_ > 1)
            outputTexture[i].elem_ /= static_cast<unsigned char>(outputTexture[i].counter_);
        else
            if (outputTexture[i].counter_ <= 0)
                ++unhitPixels;
    }
    std::cout << "# streamlines = " << numStreamlines << ", # unhit pixels = " << unhitPixels
        << " (" << static_cast<float>(100 * unhitPixels) / static_cast<float>(numPixels) << " %)\n";

    T* result = new T[numPixels];
    memcpy(result, outputTexture.getData(), sizeof(T) * numPixels);
    return result;

}
